package draft;

import java.util.ArrayList;

import java.util.Collections;
import java.util.HashMap;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import draft.Player.Position;


/**
 * Minimax.java
 * 
 * Heuristic-based alpha-beta pruning mimimax search implementation for.
 * 
 * CSE 511A, Fall 2009, Project
 * Fantasy League Team Manager
 * @author Doug Hyde, hyde@wustl.edu
 * @author Jon Wald, 
 * @author Will
 * @author Andrew
 */

public class Minimax {
	
	private static final int CORES = 2; //number of processor cores on machine for 
	private static final int NUM_PLAYERS = 3;
	private static final int DEPTH = 5;
	
	public static League searchLeague;
	private static HashMap<Position, ArrayList<Player>> allPlayers;
	public static int count = 0;

	
	public Minimax () {
		searchLeague = new League();
		allPlayers = new HashMap<Position, ArrayList<Player>>();
		for (Position p : Position.values())
			allPlayers.put(p, new ArrayList<Player>());
		for (Player p : FantasyManager.players)
			allPlayers.get(p.position).add(p);
		for (Position p : Position.values())
			Collections.sort(allPlayers.get(p), Collections.reverseOrder()); // high to low
	}
	
	
	/**
	 * Record moves actually made to the searchLeague to update state.
	 * @param a Action just made
	 * @param team Index of team who drafted player
	 */
	public static void addPlayer (Player p, int team) {
		searchLeague.addPlayer(p, team);
		allPlayers.get(p.position).remove(p);
	}
	
	
	/**
	 * Runs Minimax search to find the move with the best expected outcome.
	 * @return next move to make 
	 */
	public Player search (int team) {
		if (allPlayers == null || allPlayers.size() == 0)
			return null;
		for (Player a : topPlayers())
			a.value = 0; // Reset values
		
		// Run draft
		long startTime = System.currentTimeMillis();
		double[] values = maxValue(team, 0);
		long ellapsedTime = System.currentTimeMillis() - startTime;
		for (double d : values)
			System.out.print(d + " ");
		System.out.println();
		
		// Display runtime performance information
		System.out.println("\nRuntime Performance ---\n" +
				"Runtime: "+ ellapsedTime + "\n" +
				"Visited Nodes: " + count + "\n");
		
		// Get optimal pick
		if (topPlayers().size() == 0)
			return new Player("Error", Position.QB, 0);
		Player p = topPlayers().get(0);
		for (Player a : topPlayers())
			if (a.value > p.value)
				p = a;
		System.out.println("Done, recommding: " + p);
		return p;
	}
	
	
	/**
	 * @param index of team to play
	 * @param depth of the search
	 * @return evalution for all players with given action
	 */
	private double[] maxValue (int team, int depth) {
		if (cutoffTest(depth))
			return evaluation();
		team %= 2 * League.size;
		int t = team < League.size ? team : 2 * League.size - 1 - team;
		double[] v = new double[League.size];
		v[t] = Double.NEGATIVE_INFINITY;

		for (Player p : topPlayers()) {
			++count;
			pushAction(p, t);
				double[] actionValues = maxValue(team+1, depth+1);
				if (actionValues[t] > v[t])
					v = actionValues;
			popAction(p, t);
			if (depth == 0) // Root level decision
				p.value = v[t];
		}
		return v;
	}
	
	
	/**
	 * Gets the best NUM_PLAYERS for each position and returns an ArrayList.  */
	private ArrayList<Player> topPlayers() {
		ArrayList<Player> out = new ArrayList<Player>();
		Player temp;
		for (Position p : Position.values()) {
			
			for (int i=0; i<NUM_PLAYERS && i < allPlayers.get(p).size(); ++i) {
				temp = allPlayers.get(p).get(i);
				if (!temp.visited){
					out.add(temp);
				}
			}
		}
		return out;
	}
	
	
	/**
	 * Pushes action onto stack
	 * @param p player considered
	 * @param team Index of team drafting
	 */
	private void pushAction(Player p, int team) {
		p.visited = true;
		searchLeague.addPlayer(p, team);
	}
	
	
	/**
	 * Removes action from the stack
	 * @param p player considered
	 * @param team Index of team drafting
	 */
	private void popAction (Player p, int team) {
		searchLeague.removePlayer(p, team);
		p.visited = false;
	}
	
	
	/**
	 * @param depth of search at current node
	 * @return Whether the cutoff should take place
	 */
	private boolean cutoffTest (int depth) {
		return searchLeague.movesLeft == 0 || depth >= DEPTH;
	}

	
	/** @return the utility of the current state of the search space */
	private double[] evaluation () {
		if(CORES > 3){
			double[] rating = new double[League.size];
			double[] evals = new double[League.size];
			
			double max = Double.MIN_VALUE;
			//AtomicInteger max = Integer.MIN_VALUE; 
			ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(5);
		    ThreadPoolExecutor executor = new ThreadPoolExecutor(CORES, CORES,
		                360, TimeUnit.SECONDS, queue);
	
			for (int i=0; i<League.size; i=i+2) {
				//rating[i] = searchLeague.teams[i].evaluate();
				executor.execute(new EvaluateTeamCommand(rating, i));
			}
			executor.shutdown();
			try {
				executor.awaitTermination(360, TimeUnit.SECONDS);
			} catch (InterruptedException e) {
				System.out.print("Error: evaluating state took too long");
				e.printStackTrace();
			}
			
			for (int i=0; i<League.size; ++i) {
				max = Math.max(max, rating[i]);
			}
			
			
			// TODO this only gets the difference between first and each team
			for (int i=0; i<League.size; ++i) {
				evals[i] = rating[i] - max;
			}
			
			return evals;
		}
		double[] rating = new double[League.size];
		double[] evals = new double[League.size];
		
		double max = Double.MIN_VALUE;
		for (int i=0; i<League.size; ++i) {
			rating[i] = searchLeague.teams[i].evaluate();
			max = Math.max(max, rating[i]);
		}
		
		// TODO this only gets the difference between first and each team
		for (int i=0; i<League.size; ++i) {
			evals[i] = rating[i] - max;
		}
		
		return evals;
	}

	public static int getUndraftedValue(Position p){
		int sum = 0;
		int players = Math.min(10, allPlayers.get(p).size());
		if(players == 0){
			return 150;
		}
		for(int i = 0; i < players; ++i){
			sum += allPlayers.get(p).get(i).rating;
		}
		return sum/players;
	}
	
}
